#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <array>
#include <cstdint>
#include <math.h>

#include "predefined.hpp"
#include "input.hpp"
#include "global.hpp"
#include "filereader.hpp"

using namespace std;

Input::Input(){
    m_msg.reserve(MAXCHAR);
}
Input::~Input(){

}

void Input::readcaseCtrl(string casefileName){
    FileReader opts(casefileName);

    logCtrl.info("-------------- Reading log settings -------------");
    opts.getScalarValue("logLevel",logLevel,logCtrl.levelInfo);
    logCtrl.setLevel(logLevel);
    opts.getScalarValue("monitorFreq",monitorFreq,1);
    opts.getScalarValue("monitorScreen",monitorScreen,0);
    logCtrl.setModeScreen(monitorScreen);
    opts.getScalarValue("monitorFile",monitorFile,1);
    logCtrl.setModeFile(monitorFile);

    logCtrl.info("-------------- Reading physical dimensions -------------");
    opts.getScalarValue("physDim",physDim,3);

    logCtrl.info("-------------- Reading restart settings -------------");
    opts.getScalarValue("restartFlag",restartFlag,0);
    opts.getScalarValue("restartFreq",restartFreq,INT32_MAX);

    logCtrl.info("-------------- Reading mesh settings -------------");
    opts.getScalarValue("meshFileName",meshFileName,string("./mesh.cgns"));
    opts.getScalarValue("oversetMeshTabFileName",oversetMeshTabFileName,string("./oversetMeshTab"));
    opts.getScalarValue("oversetMeshInfoFileName",oversetMeshInfoFileName,string("./oversetMeshInfo"));

    logCtrl.info("-------------- Reading material settings -------------");
    opts.getScalarValue("Cp",Cp,1005.0);
    opts.getScalarValue("Cv",Cv,718.0);
    opts.getScalarValue("gamma",gamma,1.4);
    opts.getScalarValue("S_gas",S_gas,110.4);
    opts.getScalarValue("T_gas",T_gas,273.15);
    opts.getScalarValue("R_gas",R_gas,287.0);
    opts.getScalarValue("Tk_gas",Tk_gas,194.0);
    opts.getScalarValue("kappa_gas",kappa_gas,0.02415);
    opts.getScalarValue("mu_gas",mu_gas,1.827E-5);
    opts.getScalarValue("muB_mu",muB_mu,0.6*mu_gas);

    logCtrl.info("-------------- Reading meanflow settings -------------");
    opts.getScalarValue("meanflowFlag",meanflowFlag,0);
    opts.getScalarValue("meanflowFileName",meanflowFileName,string("meanflow.cgns"));
    opts.getScalarValue("T_ic",T_ic,288.15);
    opts.getScalarValue("rho_ic",rho_ic,1.293);
    opts.getVectorValue("v_ic",v_ic,v_ic_default);
    opts.getScalarValue("p_ic",p_ic,rho_ic*R_gas*T_ic);

    logCtrl.info("-------------- Reading output settings -------------");
    opts.getScalarValue("outFileName",outFileName,string("Sol_"));
    opts.getScalarValue("outTyp",outTyp,0);
    opts.getScalarValue("outFreq",outFreq,INT32_MAX);
    opts.getScalarValue("outStart",outStart,0);
    opts.getScalarValue("outPointNum",outPointNum,2);
    opts.getMatrixValue("outPointPos", outPointNum, MAXDIM, &outPointPos[0][0]);

    logCtrl.info("-------------- Reading iteration setting parameters -------------");
    opts.getScalarValue("maxStep",maxStep,INT32_MAX);
    opts.getScalarValue("CFL",CFL,0.5);
    opts.getScalarValue("dt",dt,-1.0); 

    logCtrl.info("-------------- Reading filter settings -------------");
    opts.getScalarValue("filterCoe",filterCoe,0.1);
    opts.getScalarValue("filterFreq",filterFreq,1);
    
    logCtrl.info("-------------- Reading PML settings -------------");
    opts.getScalarValue("beta",beta,-1.0);
    opts.getScalarValue("sgMax",sgMax,2.0);

    logCtrl.info("-------------- Reading non-dimensionalization parameters -------------");
    opts.getScalarValue("L_inf",L_inf,1.0);
    opts.getScalarValue("T_inf",T_inf,288.15);
    opts.getScalarValue("rho_inf",rho_inf,1.293);
    
    logCtrl.info("-------------- Reading source settings -------------");
    opts.getScalarValue("srcTyp",srcTyp,100); 
    opts.getScalarValue("srcSPL",srcSPL,120.0);
    opts.getScalarValue("srcFreq",srcFreq,1000.0);
    opts.getScalarValue("srcRadius",srcRadius,0.1);
    opts.getVectorValue("srcPointPos",srcPointPos,srcPointPos_default);
    if (srcTyp==110) opts.getMatrixValue("srcLinePos",2,MAXDIM ,&srcLinePos[0][0]);
    if (srcTyp==120) opts.getVectorValue("srcFacePos",srcFacePos,srcFacePos_default);
    if (srcTyp>=200) opts.getVectorValue("srcMode",srcMode,srcMode_default);

    logCtrl.info("-------------- Reading boudary conditions -------------");
    opts.getScalarValue("bcTypNum",bcTypNum,0);
    if (bcTypNum>0) opts.getMatrixValue("bcTyp",bcTypNum,MAXFACE+1,&bcTyp[0][0]);
    opts.getScalarValue("NPeriod",NPeriod,0);
    if (NPeriod>0) opts.getMatrixValue("bcPeriod", NPeriod, 4+4*NDIM, &bcPeriod[0][0]);
    opts.getScalarValue("impedanceFileName",impedanceFileName,string("impedance"));
    opts.closeFile();
}


void Input::setupParams(){
    /* Check errors */
    if (outFreq == 0)     outFreq = (int)INFINITY;
    if (restartFreq == 0) restartFreq = (int)INFINITY;
    if (monitorFreq == 0) monitorFreq = (int)INFINITY;
    if (monitorFreq>outFreq) monitorFreq=outFreq;
    
    /* Non-dimensionalization */
    p_inf=rho_inf*R_gas*T_inf;
    c_inf=sqrt(gamma*R_gas*T_inf);
    gamma=Cp/Cv;
    kappa_inf=kappa_gas*pow((T_inf/T_gas),1.50)*(T_gas+Tk_gas)/(T_inf+Tk_gas);//Sutherland’s Law Parameters for Thermal Conductivity
    mu_inf=mu_gas*pow(T_inf/T_gas,1.5)*((T_gas+S_gas)/(T_inf+S_gas));//Sutherland’s Law Parameters for Dynamic Viscosity
    Pr=Cp*mu_inf/kappa_inf;
    
    if ((p_ic/R_gas/T_ic-rho_ic)/rho_ic>0.05) logCtrl.warning("The initial conditions is not for a ideal gas.");
    mu_ic=mu_gas*pow(T_ic/T_gas,1.5)*((T_gas+S_gas)/(T_ic+S_gas));
    kappa_ic=kappa_gas*pow((T_ic/T_gas),1.50)*(T_gas+Tk_gas)/(T_ic+Tk_gas);

    srcPeff=pow(10.0,srcSPL/20.0)*2e-5;

    logCtrl.info("------------------ Non-dimensionalization -----------------");
    L_ref=L_inf;
    T_ref=T_inf;
    rho_ref=rho_inf;
    v_ref=sqrt(gamma*R_gas*T_ref);
    time_ref=L_ref/v_ref;
    p_ref=rho_ref*v_ref*v_ref;
    mu_ref=mu_inf;
    kappa_ref=kappa_inf;
    Re=rho_ref*v_ref*L_ref/mu_ref;
    logCtrl.info("Initial condition: rho        p        T        mu        kappa");
    m_msg="              "+to_string(rho_ic)+"  "+to_string(p_ic)+"  "+to_string(T_ic)+"  "+to_string(mu_ic)+"  "+to_string(kappa_ic);
    logCtrl.info(m_msg.c_str());
    m_msg="Initial velocity field: ";
    for (int i=0;i<MAXDIM;i++) m_msg+=to_string(v_ic[i])+",  ";
    logCtrl.info(m_msg.c_str());

    c_ic=sqrt(gamma*R_gas*T_ic)/v_ref;
    Ma_ic=max(v_ic[0],v_ic[1])/sqrt(gamma*R_gas*T_ic);
    T_ic/=T_ref;
    rho_ic/=rho_ref;
    p_ic/=p_ref;
    mu_ic/=mu_ref;
    kappa_ic/=kappa_ref;
    for (int i=0;i<MAXDIM;i++) v_ic[i]/=v_ref;
    srcPeff/=p_ref;
    srcPamp=srcPeff*sqrt(2.0);
    logCtrl.info("Non-dimensional initial condition: rho      p      T      mu      kappa");
    m_msg="                  "+to_string(rho_ic)+"  "+to_string(p_ic)+"  "+to_string(T_ic)+"  "+to_string(mu_ic)+"  "+to_string(kappa_ic);
    logCtrl.info(m_msg.c_str());
    m_msg="Non-dimensional initial velocity field: ";
    for (int i=0;i<MAXDIM;i++) m_msg+=to_string(v_ic[i])+",  ";
    logCtrl.info(m_msg.c_str());

    logCtrl.info("Ma     Re_c       Re_v       Pr");
    m_msg=to_string(Ma_ic)+"  "+to_string(Re)+"  "+to_string(Re*v_ic[0])+"  "+to_string(Pr);
    logCtrl.info(m_msg.c_str());

    if (beta<0) beta=v_ic[0]/(1-v_ic[0]*v_ic[0]);
    for (int i=0;i<MAXDIM;i++) srcPointPos[i]/=L_ref;
    srcRadius/=L_ref;
    srcFreq*=time_ref;
    omega=2*math.pi*srcFreq;
    k=omega/c_ic;
    logCtrl.info("frequency      omega      k");
    m_msg=to_string(srcFreq)+"  "+to_string(omega)+"  "+to_string(k);
    logCtrl.info(m_msg.c_str());
    
    outputCtrl.outTyp=inputCtrl.outTyp;
    outputCtrl.outFreq=inputCtrl.outFreq;
    outputCtrl.outFileName=inputCtrl.outFileName;
    outputCtrl.outSt=inputCtrl.outStart;
}

template<typename T>
void Input::checkRead(string optName, T &assignedVal){
    stringstream infoMsg;
    infoMsg<<optName<<": "<<assignedVal<<endl;
    logCtrl.info((infoMsg.str()).c_str());
}

//Due to template fuctions, the definition of class FileReader must be included here 
//then the class Input can use the functions, otherwise a link error can not be avoided.
FileReader::FileReader(){

}

FileReader::FileReader(string fileName)
{
    this->m_fileName = fileName;
}

FileReader::~FileReader()
{
    if (m_fileStream.is_open()) m_fileStream.close();
}

void FileReader::setFile(string fileName)
{
    this->m_fileName = fileName;
}

void FileReader::openFile(void)
{
    m_fileStream.open(m_fileName, ifstream::in);
}

void FileReader::closeFile()
{
    m_fileStream.close();
}

template<typename T>
void FileReader::getScalarValue(string optName, T &assignedVal, T defaultVal)
{
    string str,warnMsg,optKey;
    stringstream infoMsg;
    FileReader::openFile();
    if (!m_fileStream.is_open() || !getline(m_fileStream,str)) {
        m_fileStream.open(m_fileName);
        if (!m_fileStream.is_open())logCtrl.fatalError("Cannont open input file for reading.");
    }
    // Rewind to the start of the file
    m_fileStream.clear();
    m_fileStream.seekg(0,m_fileStream.beg);
    // Search for the given option string
    while (getline(m_fileStream,str)) {
    // Remove any leading whitespace & see if first word is the input option
        stringstream ss;
        ss.str(str);
        ss >> optKey;
        if (optKey.compare(optName)==0) {
            if (!(ss >> assignedVal)) {
                // This could happen if, for example, trying to assign a string to a double
                warnMsg="Unable to assign value to option "+optName+", using default value instead.";
                logCtrl.warning(warnMsg.c_str());
                assignedVal = defaultVal; 
            }
            infoMsg<<optName<<": "<<assignedVal;
            logCtrl.info((infoMsg.str()).c_str());
            closeFile();
            return;
        }
    }
    warnMsg="Required option "+optName+" not found"+", using default value instead.";
    logCtrl.warning(warnMsg.c_str());
    assignedVal = defaultVal;  
    infoMsg<<optName<<": "<<assignedVal;
    logCtrl.info((infoMsg.str()).c_str());
    FileReader::closeFile();
}

template<typename T>
void FileReader::getScalarValue(string optName, T& assignedVal){
    string str, errMsg, optKey;
    stringstream infoMsg;
    FileReader::openFile();
    if (!m_fileStream.is_open()) {
        m_fileStream.open(m_fileName);
        if (!m_fileStream.is_open()) logCtrl.fatalError("Can not open input file for reading.");
    }
    // Rewind to the start of the file
    m_fileStream.clear();
    m_fileStream.seekg(0,m_fileStream.beg);
    // Search for the given option string
    while (getline(m_fileStream,str)) {
        // Remove any leading whitespace & see if first word is the input option
        stringstream ss;
        ss.str(str);
        ss >> optKey;
        if (optKey.compare(optName)==0) {
            if (!(ss >> assignedVal)) {
                // This could happen if, for example, trying to assign a string to a double
                errMsg="Unable to assign value to option "+optName;
                logCtrl.fatalError(errMsg.c_str());
            }
            infoMsg<<optName<<": "<<assignedVal;
            logCtrl.info((infoMsg.str()).c_str());
            closeFile();
            return;
        }
    }
    // Option was not found; throw error & exit
    errMsg = "Required option not found: " + optName;
    logCtrl.fatalError(errMsg.c_str());
}

template<typename T>
void FileReader::getVectorValue(string optName, T &opt, T & defaultVal)
{ 
    string str, optKey,warnMsg;
    stringstream infoMsg;
    FileReader::openFile();
    if (!m_fileStream.is_open()) {
        m_fileStream.open(m_fileName);
        if (!m_fileStream.is_open()) logCtrl.fatalError("Can not open input file for reading.");
    }
  
    // Rewind to the start of the file
    m_fileStream.clear();
    m_fileStream.seekg(0,m_fileStream.beg);
  
    // Search for the given option string
    while (getline(m_fileStream,str)) {
        // Remove any leading whitespace & see if first word is the input option
        stringstream ss;
        ss.str(str);    
        ss >> optKey;
        if (optKey.compare(optName)==0) {
            for (int i=0; i<MAXDIM; i++){
                if (!(ss >> opt[i])) {
                    warnMsg="Unable to assign value to option "+optName+", using default value instead.";
                    logCtrl.warning(warnMsg.c_str());
                    for (int i=0; i<MAXDIM; i++){
                        opt[i] = defaultVal[i];
                    }
                }
            }
            infoMsg<<optName<<": ";
            for (int i=0; i<MAXDIM; i++){
                infoMsg<<opt[i]<<" ";
            }
            //cout<<"Read:"<<ss.str()<<endl;
            logCtrl.info((infoMsg.str()).c_str());
            FileReader::closeFile();
            return;
        }
    }
    warnMsg="Required option "+optName+" not found"+", using default value instead.";
    logCtrl.warning(warnMsg.c_str());
    for (int i=0; i<MAXDIM; i++){
        opt[i] = defaultVal[i];
    } 
    infoMsg<<optName<<": ";
            for (int i=0; i<MAXDIM; i++){
                infoMsg<<opt[i]<<" ";
    }
    logCtrl.info((infoMsg.str()).c_str());
    FileReader::closeFile();
}

template<typename T>
void FileReader::getVectorValue(string optName, T &opt)
{ 
    string str, optKey,errMsg;
    stringstream infoMsg;
    FileReader::openFile();
    if (!m_fileStream.is_open()) {
        m_fileStream.open(m_fileName);
        if (!m_fileStream.is_open()) logCtrl.fatalError("Can not open input file for reading.");
    }
  
    // Rewind to the start of the file
    m_fileStream.clear();
    m_fileStream.seekg(0,m_fileStream.beg);
  
    // Search for the given option string
    while (getline(m_fileStream,str)) {
        // Remove any leading whitespace & see if first word is the input option
        stringstream ss;
        ss.str(str);    
        ss >> optKey;
        if (optKey.compare(optName)==0) {
            for (int i=0; i<MAXDIM; i++){
                if (!(ss >> opt[i])) {
                    errMsg = "Unable to assign all values to vector option " + optName ;
                    logCtrl.fatalError(errMsg.c_str());
                }
            }
            infoMsg<<optName<<": ";
            for (int i=0; i<MAXDIM; i++){
                infoMsg<<opt[i]<<" ";
            }
            //cout<<"Read:"<<ss.str()<<endl;
            logCtrl.info((infoMsg.str()).c_str());
            FileReader::closeFile();
            return;
        }
    }
    // Option was not found; throw error & exit
    errMsg = "Required option not found: " + optName;
    logCtrl.fatalError(errMsg.c_str());
}

template<typename T>
void FileReader::getMatrixValue(string optName,  int nRow, int nColumn, T *matrix)
{ 
    string str, optKey,errMsg;
    stringstream infoMsg;
    FileReader::openFile();
    if (!m_fileStream.is_open()) { 
        m_fileStream.open(m_fileName);
        if (!m_fileStream.is_open()) logCtrl.fatalError("Can not open input file for reading.");
    }
  
    // Rewind to the start of the file
    m_fileStream.clear();
    m_fileStream.seekg(0,m_fileStream.beg);
  
    // Search for the given option string
    while (getline(m_fileStream,str)) {
        // Remove any leading whitespace & see if first word is the input option
        stringstream ss;
        ss.str(str);    
        ss >> optKey;
        if (optKey.compare(optName)==0) {
            //if (!(ss >> nRow)) {
            //    errMsg = "Unable to assign all values to matrix option " + optName ;
            //    logCtrl.fatalError(errMsg.c_str());
            //}
            //infoMsg<<optName<<": "<<nRow;
            //logCtrl.info((infoMsg.str()).c_str());
            //cout<<infoMsg.str()<<endl;
            for (int i=0; i<nRow; i++){
                //cout<<i<<endl;
                stringstream sMatrix;
                infoMsg.str("");
                //ss.str("");
                infoMsg<<optName<<" "<<i<<": ";
                getline(m_fileStream,str);
                sMatrix.str(str);
                //cout<<"Read: "<<sMatrix.str()<<endl;  
                for (int j=0;j<nColumn;j++){
                    if (!(sMatrix >> matrix[i * nColumn + j])) {
                        errMsg = "Unable to assign all values to matrix " + to_string(i) + ", " +  to_string(j) ;
                        logCtrl.fatalError(errMsg.c_str());
                    }
                    infoMsg<<(matrix[i*nColumn+j])<<" ";
                }
                //cout<<infoMsg.str()<<endl;
                logCtrl.info((infoMsg.str()).c_str());
            }        
            FileReader::closeFile();
            return;
        }
    }
    // Option was not found; throw error & exit
    errMsg = "Required option not found: " + optName;
    logCtrl.fatalError(errMsg.c_str());
}



void FileReader::getIntMatrixValue(string optName,  int nRow, int nColumn, int *matrix)
{ 
    string str, optKey,errMsg;
    stringstream infoMsg;
    FileReader::openFile();
    if (!m_fileStream.is_open()) { 
        m_fileStream.open(m_fileName);
        if (!m_fileStream.is_open()) logCtrl.fatalError("Can not open input file for reading.");
    }
  
    // Rewind to the start of the file
    m_fileStream.clear();
    m_fileStream.seekg(0,m_fileStream.beg);
  
    // Search for the given option string
    while (getline(m_fileStream,str)) {
        // Remove any leading whitespace & see if first word is the input option
        stringstream ss;
        ss.str(str);    
        ss >> optKey;
        if (optKey.compare(optName)==0) {
            //if (!(ss >> nRow)) {
            //    errMsg = "Unable to assign all values to matrix option " + optName ;
            //    logCtrl.fatalError(errMsg.c_str());
            //}
            //infoMsg<<optName<<": "<<nRow;
            //logCtrl.info((infoMsg.str()).c_str());
            //cout<<infoMsg.str()<<endl;
            for (int i=0; i<nRow; i++){
                //cout<<i<<endl;
                stringstream sMatrix;
                infoMsg.str("");
                //ss.str("");
                infoMsg<<optName<<" "<<i<<": ";
                getline(m_fileStream,str);
                sMatrix.str(str);
                //cout<<"Read: "<<sMatrix.str()<<endl;  
                for (int j=0;j<nColumn;j++){
                    if (!(sMatrix >> matrix[i * nColumn + j])) {
                        errMsg = "Unable to assign all values to matrix " + to_string(i) + ", " +  to_string(j) ;
                        logCtrl.fatalError(errMsg.c_str());
                    }
                    infoMsg<<(matrix[i*nColumn+j])<<" ";
                }
                //cout<<infoMsg.str()<<endl;
                logCtrl.info((infoMsg.str()).c_str());
            }        
            FileReader::closeFile();
            return;
        }
    }
    // Option was not found; throw error & exit
    errMsg = "Required option not found: " + optName;
    logCtrl.fatalError(errMsg.c_str());
}

void FileReader::getDoubleMatrixValue(string optName,  int nRow, int nColumn, double *matrix)
{ 
    string str, optKey,errMsg;
    stringstream infoMsg;
    FileReader::openFile();
    if (!m_fileStream.is_open()) { 
        m_fileStream.open(m_fileName);
        if (!m_fileStream.is_open()) logCtrl.fatalError("Can not open input file for reading.");
    }
  
    // Rewind to the start of the file
    m_fileStream.clear();
    m_fileStream.seekg(0,m_fileStream.beg);
  
    // Search for the given option string
    while (getline(m_fileStream,str)) {
        // Remove any leading whitespace & see if first word is the input option
        stringstream ss;
        ss.str(str);    
        ss >> optKey;
        if (optKey.compare(optName)==0) {
            //if (!(ss >> nRow)) {
            //    errMsg = "Unable to assign all values to matrix option " + optName ;
            //    logCtrl.fatalError(errMsg.c_str());
            //}
            //infoMsg<<optName<<": "<<nRow;
            //logCtrl.info((infoMsg.str()).c_str());
            //cout<<infoMsg.str()<<endl;
            for (int i=0; i<nRow; i++){
                //cout<<i<<endl;
                stringstream sMatrix;
                infoMsg.str("");
                //ss.str("");
                infoMsg<<optName<<" "<<i<<": ";
                getline(m_fileStream,str);
                sMatrix.str(str);
                //cout<<"Read: "<<sMatrix.str()<<endl;  
                for (int j=0;j<nColumn;j++){
                    if (!(sMatrix >>matrix[i*nColumn+j])) {
                        errMsg = "Unable to assign all values to matrix " + to_string(i) + ", " +  to_string(j) ;
                        logCtrl.fatalError(errMsg.c_str());
                    }
                    infoMsg<<(matrix[i*nColumn+j])<<" ";
                }
                //cout<<infoMsg.str()<<endl;
                logCtrl.info((infoMsg.str()).c_str());
            }        
            FileReader::closeFile();
            return;
        }
    }
    // Option was not found; throw error & exit
    errMsg = "Required option not found: " + optName;
    logCtrl.fatalError(errMsg.c_str());
}